/*
  Copyright © 2010  Vinícius dos Santos Oliveira

      This file is part of Touché.

      Touché is free software: you can redistribute it and/or modify
      it under the terms of the GNU Lesser General Public License as published
      by the Free Software Foundation, either version 3 of the License, or
      (at your option) any later version.

      This program is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      GNU Lesser General Public License for more details.

      You should have received a copy of the GNU Lesser General Public License
      along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */

#include "stage.h"
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsScene>
#include <QPropertyAnimation>
#include "stageactor.h"
#include "stageactorlink.h"
#include "scene.h"
#include <QDebug>
#include <QFile>
#include <QXmlStreamWriter>
#include <QProcess>
using namespace Touche;

#define TOUCHE_DOCUMENT_VERSION "0"

//TODO: agora os elementos não são mais todos do mesmo tamanho, logo, os atributos abaixo deveriam ser removidos
static QSizeF m_elementSize(64., 64.);
static qreal m_elementCenter[2] = {60., 60.};
static QPointF m_elementHiddenPos(400., 212.);
static QPointF m_elementInitialPos(400., 212.);
static QPointF m_elementTrashPos(400., 424.);

void compress(const QStringList &in, const QString &out)
{
    QProcess tar;
    // create (z) a gzip (z) file (f) named ...
    tar.execute("tar -czf " + out + ' ' + in.join(QString(' ')));

    // while process not finishes
    while(tar.waitForFinished(1000));
}

// class VoidItem

class VoidItem: public QGraphicsItem
{
    friend class Stage;

protected:
    virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value)
    {
        if(change == ItemPositionChange){
            QRectF self(value.toPointF(), (1. / QGraphicsItem::scale()) * boundingRect().size());
            QRectF parent = parentItem()->boundingRect();
            if(!parent.contains(self)){
                QPointF newPos = self.topLeft();
                newPos.setX(qMin(parent.right() -self.width(), qMax(newPos.x(), parent.left())));
                newPos.setY(qMin(parent.bottom() -self.height(), qMax(newPos.y(), parent.top())));
                return newPos;
            }
        }
        return QGraphicsItem::itemChange(change, value);
    }

public:
    VoidItem(QGraphicsItem *parent) :
            QGraphicsItem(parent)
    {
        QGraphicsItem::setFlag(QGraphicsItem::ItemHasNoContents);
        QGraphicsItem::setFlag(QGraphicsItem::ItemSendsGeometryChanges);
    }

    QRectF boundingRect() const
    {
        return QRectF(0., 0., 800., 424.);
    }

    void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *)
    {
    }
};

// class Stage

QScopedPointer<Stage> Stage::m_instance;
static const QList<StageActorLink *> &elementLinks = StageActorLink::links();

Stage * Stage::makeInstance(QGraphicsItem *parentItem)
{
    if(m_instance){
        m_instance->deleteLater();
        m_instance.take();
    }
    m_instance.reset(new Stage(parentItem));
    return m_instance.data();
}

Stage * Stage::getInstance()
{
    return m_instance.data();
}

Stage::Stage(QGraphicsItem *parentItem) :
        QGraphicsObject(parentItem),
        m_items(new VoidItem(this))
{
    QGraphicsItem::setTransformOriginPoint(QGraphicsItem::mapFromScene(400., 212.));
}

Stage::~Stage()
{
    foreach(StageActor *actor, StageActor::actors()){
        actor->deleteLater();
    }
}

QRectF Stage::boundingRect() const
{
    return QRectF(-400., -212., 1600., 848.);
}

void Stage::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{
    static QPen pen(QColor(240, 206, 17), 4., Qt::DashLine);
    painter->setPen(pen);

    for(QListIterator<StageActorLink *> i(elementLinks);i.hasNext();){
        if(StageActorLink *link = i.next()) {
            QVector<StageActor *> actors = link->actors();
            QGraphicsItem *items[2] = {actors[0]->m_item,
                                       actors[1]->m_item};

            QPointF points[2] = {QGraphicsItem::mapFromItem(items[0], items[0]->boundingRect().center()),
                                 QGraphicsItem::mapFromItem(items[1], items[1]->boundingRect().center())};
            QLineF path(points[0], points[1]);
            qreal length = path.length();
            qreal itemsRadius[2] = {items[0]->boundingRect().width() / 2.,
                                     items[1]->boundingRect().width() / 2.};

            if (length > itemsRadius[0] + itemsRadius[1]) {
                QPointF edgeOffset[2] = {QPoint((path.dx() * itemsRadius[0]) / length,
                                                (path.dy() * itemsRadius[0]) / length),
                                         QPointF((path.dx() * itemsRadius[1]) / length,
                                                 (path.dy() * itemsRadius[1]) / length)};
                points[0] = path.p1() + edgeOffset[0];
                points[1] = path.p2() - edgeOffset[1];
            } else {
                points[0] = points[1] = path.p1();
            }
            path = QLineF(points[0], points[1]);
            painter->drawLine(path);
        }
    }
}

void Stage::addToGroup(Element *element)
{
    StageActor *item = new StageActor(element);
    elements.push_back(item);
    connect(item, SIGNAL(destroyed(QObject*)), this, SLOT(onElementDeleted(QObject*)));
    QGraphicsItem *gItem = item->item();

    gItem->setScale(0.);
    gItem->setTransformOriginPoint(gItem->boundingRect().center());

    gItem->setParentItem(m_items);

    gItem->setPos(m_items->mapFromScene(m_elementHiddenPos) - gItem->boundingRect().center());
    reinterpret_cast<Scene *>(item->item()->scene())->itemMoved();
    gItem->show();

    QPropertyAnimation *animation = new QPropertyAnimation(gItem->toGraphicsObject(), "scale");
    animation->setDuration(500);
    animation->setEasingCurve(QEasingCurve::OutElastic);

    animation->setEndValue(1.);
    animation->start(QAbstractAnimation::DeleteWhenStopped);
}

void Stage::setElementSize(const QSizeF &size)
{
    m_elementSize = size;
    m_elementCenter[0] = size.width() / 2.;
    m_elementCenter[1] = size.height() / 2.;
}

void Stage::setElementHiddenPos(const QPointF &pos)
{
    m_elementHiddenPos = pos;
}

void Stage::setElementInitialPos(const QPointF &pos)
{
    m_elementInitialPos = pos;
}

void Stage::setElementTrashPos(const QPointF &pos)
{
    m_elementTrashPos = pos;
}

bool Stage::save(const QString &fileName)
{
    QList<QString> documentFiles;

    QFile file("index.xml");
    if(file.open(QIODevice::WriteOnly)){
        QXmlStreamWriter writer(&file);
        writer.writeStartDocument();
        writer.writeStartElement("ToucheDiagram");
        writer.writeAttribute("version", TOUCHE_DOCUMENT_VERSION);

        foreach(StageActor *actor, StageActor::actors()){
            Element *element = actor->element();
            writer.writeStartElement("Object");
            writer.writeAttribute("type", element->type());
            writer.writeAttribute("uuid", element->id());

            {
                QString elementFile(element->file());
                if(QFile::exists(elementFile)){
                    writer.writeAttribute("file", elementFile);
                    documentFiles.append(elementFile);
                }
            }

            {
                QPointF pos(actor->m_item->pos());
                writer.writeAttribute("pos_x", QString::number(pos.x(), 'f', 2));
                writer.writeAttribute("pos_y", QString::number(pos.y(), 'f', 2));
            }

            writer.writeCharacters(element->save());
            writer.writeEndElement();
        }

        foreach(StageActorLink *link, StageActorLink::links()){
            QVector<StageActor *> actors = link->actors();

            writer.writeStartElement("ObjectLink");
            writer.writeAttribute("source", actors[0]->element()->id());
            writer.writeAttribute("sink", actors[1]->element()->id());
            writer.writeEndElement();
        }

        writer.writeEndElement();
        writer.writeEndDocument();
    }else{
        return false;
    }
    file.close();

    documentFiles.append("index.xml");

    compress(documentFiles, fileName + ".tchz");
    file.remove();

    return true;
}

void Stage::load(const QString &fileName)
{
    if(!QFile::exists(fileName))
        return;

    QProcess tar;
    tar.execute("tar xzf " + fileName);
    while(tar.waitForFinished(1000));

    QFile file("index.xml");
    file.open(QIODevice::ReadOnly);
    QXmlStreamReader xml(&file);
    bool loop = true;
    while(loop){
        switch(xml.readNext()){
        case QXmlStreamReader::NoToken:
        case QXmlStreamReader::Invalid:
            qWarning() << xml.errorString();
            loop = false;
            break;
        case QXmlStreamReader::StartDocument:
            break;
        case QXmlStreamReader::EndDocument:
            loop = false;
            break;
        case QXmlStreamReader::StartElement:
            if(xml.name() == "Object"){
                QXmlStreamAttributes attrs = xml.attributes();
                Element *element;
                if(attrs.hasAttribute("type")){
                    element = StageActor::makeElement(attrs.value("type").toString());
                    if(!element){
                        break;
                    }
                }else{
                    break;
                }
                if(attrs.hasAttribute("uuid")){
                    QUuid id = attrs.value("uuid").toString();
                    if(id.isNull()){
                        element->deleteLater();
                        break;
                    }else{
                        const_cast<QUuid &>(element->m_id) = id;
                    }
                }else{
                    element->deleteLater();
                    break;
                }
                // TODO: remover essa gambiarra muito podre fazendo-o retornar todo o xml interno
                if(attrs.hasAttribute("file")){
                    QString fileName = attrs.value("file").toString();
                    if(QFile::exists(fileName)){
                        element->load(QString(), fileName);
                    }
                }else{
                    xml.readNext();
                    element->load(xml.text().toString(), QString());
                }
                addToGroup(element);
            }else if(xml.name() == "ObjectLink"){
                QXmlStreamAttributes attrs = xml.attributes();

                if(!attrs.hasAttribute("source") || !attrs.hasAttribute("sink"))
                    break;

                QUuid srcId(attrs.value("source").toString()), sinkId(attrs.value("sink").toString());
                const QHash<QUuid, Element *> elements = StageActor::elements();

                if(!elements.contains(srcId) || !elements.contains(sinkId))
                    break;

                StageActor *source = StageActor::getActor(elements[srcId]);
                StageActor *sink = StageActor::getActor(elements[sinkId]);

                StageActorLink *link = new StageActorLink(source, sink);
                source->links.append(link);
                sink->links.append(link);
            }
            break;
        case QXmlStreamReader::EndElement:
        case QXmlStreamReader::Characters:
        case QXmlStreamReader::Comment:
        case QXmlStreamReader::DTD:
        case QXmlStreamReader::EntityReference:
        case QXmlStreamReader::ProcessingInstruction:;
        }
    }
}

QSizeF Touche::Stage::elementSize()
{
    return m_elementSize;
}

QPointF Touche::Stage::elementHiddenPos()
{
    return m_elementHiddenPos;
}

QPointF Touche::Stage::elementInitialPos()
{
    return m_elementInitialPos;
}

void Stage::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    QGraphicsItem::mousePressEvent(event);
    pressedAt = event->screenPos();
    event->accept();
}

void Stage::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    QGraphicsItem::mouseReleaseEvent(event);
    if(StageActor::opened){
        StageActor::opened->closeMenu();
    }
}

void Stage::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    QGraphicsItem::mouseMoveEvent(event);
    // TODO: fórmula da linha abaixo
//    m_items->setPos(QGraphicsItem::mapFromItem(this, event->pos()));
    QGraphicsItem::update();
}

void Stage::onElementDeleted(QObject *element)
{
    elements.removeAt(elements.indexOf(reinterpret_cast<StageActor *>(element)));
}
